package com.yea.loadbalancer;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.yea.core.loadbalancer.INodeListUpdater;
import com.yea.loadbalancer.config.CommonClientConfigKey;
import com.yea.loadbalancer.config.IClientConfig;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * A default strategy for the dynamic server list updater to update. (refactored
 * and moved here from
 * {@link com.netflix.loadbalancer.DynamicServerListLoadBalancer})
 *
 * @author David Liu
 */
public class PollingServerListUpdater implements INodeListUpdater {

	private static final Logger logger = LoggerFactory.getLogger(PollingServerListUpdater.class);

	private static long LISTOFSERVERS_CACHE_UPDATE_DELAY = 1000; // msecs;
	private static int LISTOFSERVERS_CACHE_REPEAT_INTERVAL = 30 * 1000; // msecs;

	private static class LazyHolder {
		private static Thread _shutdownThread;
		static ScheduledThreadPoolExecutor _serverListRefreshExecutor = null;
		static {
			int coreSize = 2;
			ThreadFactory factory = (new ThreadFactoryBuilder()).setNameFormat("PollingServerListUpdater-%d")
					.setDaemon(false).build();
			_serverListRefreshExecutor = new ScheduledThreadPoolExecutor(coreSize, factory);
			_shutdownThread = new Thread(new Runnable() {
				public void run() {
					logger.info("Shutting down the Executor Pool for PollingServerListUpdater");
					shutdownExecutorPool();
				}
			});
			Runtime.getRuntime().addShutdownHook(_shutdownThread);
		}

		private static void shutdownExecutorPool() {
			if (_serverListRefreshExecutor != null) {
				_serverListRefreshExecutor.shutdown();

				if (_shutdownThread != null) {
					try {
						Runtime.getRuntime().removeShutdownHook(_shutdownThread);
					} catch (IllegalStateException ise) { // NOPMD
						// this can happen if we're in the middle of a real
						// shutdown,
						// and that's 'ok'
					}
				}

			}
		}
	}

	private static ScheduledThreadPoolExecutor getRefreshExecutor() {
		return LazyHolder._serverListRefreshExecutor;
	}

	private final AtomicBoolean isActive = new AtomicBoolean(false);
	private volatile long lastUpdated = System.currentTimeMillis();
	private final long initialDelayMs;
	private final long refreshIntervalMs;

	private volatile ScheduledFuture<?> scheduledFuture;

	public PollingServerListUpdater() {
		this(LISTOFSERVERS_CACHE_UPDATE_DELAY, LISTOFSERVERS_CACHE_REPEAT_INTERVAL);
	}

	public PollingServerListUpdater(IClientConfig clientConfig) {
		this(LISTOFSERVERS_CACHE_UPDATE_DELAY, getRefreshIntervalMs(clientConfig));
	}

	public PollingServerListUpdater(final long initialDelayMs, final long refreshIntervalMs) {
		this.initialDelayMs = initialDelayMs;
		this.refreshIntervalMs = refreshIntervalMs;
	}

	@Override
	public synchronized void start(final UpdateAction updateAction) {
		if (isActive.compareAndSet(false, true)) {
			final Runnable wrapperRunnable = new Runnable() {
				@Override
				public void run() {
					if (!isActive.get()) {
						if (scheduledFuture != null) {
							scheduledFuture.cancel(true);
						}
						return;
					}
					try {
						updateAction.doUpdate();
						lastUpdated = System.currentTimeMillis();
					} catch (Exception e) {
						logger.warn("Failed one update cycle", e);
					}
				}
			};

			scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(wrapperRunnable, initialDelayMs,
					refreshIntervalMs, TimeUnit.MILLISECONDS);
		} else {
			logger.info("Already active, no-op");
		}
	}

	@Override
	public synchronized void stop() {
		if (isActive.compareAndSet(true, false)) {
			if (scheduledFuture != null) {
				scheduledFuture.cancel(true);
			}
		} else {
			logger.info("Not active, no-op");
		}
	}

	@Override
	public String getLastUpdate() {
		return new Date(lastUpdated).toString();
	}

	@Override
	public long getDurationSinceLastUpdateMs() {
		return System.currentTimeMillis() - lastUpdated;
	}

	@Override
	public int getNumberMissedCycles() {
		if (!isActive.get()) {
			return 0;
		}
		return (int) ((int) (System.currentTimeMillis() - lastUpdated) / refreshIntervalMs);
	}

	@Override
	public int getCoreThreads() {
		if (isActive.get()) {
			if (getRefreshExecutor() != null) {
				return getRefreshExecutor().getCorePoolSize();
			}
		}
		return 0;
	}

	private static long getRefreshIntervalMs(IClientConfig clientConfig) {
		return clientConfig.get(CommonClientConfigKey.ServerListRefreshInterval, LISTOFSERVERS_CACHE_REPEAT_INTERVAL);
	}
}
